Se nekaj terminologije, ki bi bila lahko ze v prejsnjem mailu, ce bi se jaz
spomnil nanjo: pri povezavi u->v ali u:v recemo, da sta u in v "krajisci" te
povezave.  Ce je povezava usmerjena, u->v, pravimo, da je u zacetno
krajisce, v pa koncno.

Mislimo si usmerjen aciklicen graf.  Recimo, da bi tocke zapisali v taksnem
vrstnem redu, da pride zacetno krajisce vsake povezave v tem vrstnem redu
pred koncnim krajiscem tiste povezave.  Taksnemu vrstnemu redu pravimo
"topoloski vrstni red", postopku, s katerim tak vrstni red dobimo, pa
pravimo "topolosko urejanje".

Z drugimi besedami: ce bi tocke grafa polozili na neko premico v topoloskem
vrstnem redu od leve proti desni, bi potem vse povezave kazale v smeri proti
desni.

Potem vemo, da je mogoce od tocke u priti samo v tiste, ki so v topoloskem
vrstnem redu za njo (pa se to ne v vse take), pa v nobeno od tistih, ki so v
topoloskem vrstnem redu pred njo (ker nam vse povezave omogocajo priti samo
naprej (proti koncu) topoloskega vrstnega reda, ne pa nazaj (proti
zacetku)).

Topoloski vrstni red nam omogoca na eleganten in sistematicen nacin poceti
nekatere stvari, ki se opirajo na premikanje oz. dosegljivost v grafu.  Za
povrhu je topolosko urejanje preprost in casovno nezahteven postopek.

Mimogrede, v grafu topoloski vrstni red ni nujno enolicno dolocen.  Mislimo
si "karo" -- graf s tockami a, b, c, d in povezavami a->b, a->c, b->d, c->d.
Potem je primeren topoloski vrstni red (a, b, c, d), dober pa je tudi (a, c,
d, b).  Karkoli ze pac mislimo s topoloskim vrstnim redom poceti, je
obicajno vseeno, katerega od moznih topoloskih vrstnih redov vzamemo.

--

Katera tocka je lahko prva v topoloskem vrstnem redu?  Gotovo le taka, v
katero ne kaze nobena povezava -- kajti ce bi, bi moralo zacetno krajisce te
povezave priti v topoloskem vrstnem redu se pred naso tocko.  Med takimi, v
katere ne kaze nobena povezava, pa lahko izberemo katero koli in jo
postavimo na prvo mesto -- ker je nasa tocka na prvem mestu, bodo povezave,
ki izhajajo iz nje, gotovo sle v tocke, ki bodo v vrstnem redu kasneje, zato
bo pogoju iz definicije topoloskega vrstnega reda ustrezeno.

Mogoce bi koga zaskrbelo, kako vemo, da obstaja kaksna tocka, v katero ne
kaze nobena povezava.  No, pa recimo, da take ni -- torej da v vsako tocko
kaze vsaj ena povezava.  Vsaka tocka ima torej vsaj eno predhodnico.  Pa
zacnimo pri poljubni tocki, recimo ji u[0].  Naj bo u[1] kaksna predhodnica
tocke u[0] (saj smo predpostavili, da u[0] gotovo ima kaksno predhodnico).
Naj bo u[2] kaksna predhodnica tocke u[1] (tudi u[1] ima namrec po nasi
predpostavki gotovo kaksno predhodnico).  Naj bo u[3] kaksna predhodnica
tocke u[2] in tako naprej.  Tako dobivamo nove in nove tocke, ker pa ima nas
graf le koncno mnogo tock, se nam morajo prej ali slej zaceti ponavljati.
Takrat pa bomo torej ugotovili, da ima u[i] neko predhodnico u[i+1], ta neko
predhodnico u[i+2], ..., u[j-1] pa ima neko predhodnico u[j], ki je ista
tocka kot u[i].  To pa pomeni, da obstaja povezava od u[j] v u[j-1], pa od
u[j-1] do u[j-2], ..., in koncno od u[i+1] do u[i], kar pa je isto kot od
u[i+1] do u[j].  Torej obstaja v nasem grafu cikel:
u[j]->u[j-1]->...->u[i+1]->u[j].  To pa je v protislovju s predpostavko, da
imamo aciklicen graf.  Torej se to ne more zgoditi -- gotovo je kaksna tocka
v grafu brez predhodnika.

No, zdaj, ko smo izbrali neko tocko in jo postavili na prvo mesto v
topoloskem vrstnem redu, smo se tudi ze prepricali, da s povezavami, ki
kazejo iz nje, ne bo nobenih tezav (da bi krsile pogoj iz definicije
topoloskega vrstnega reda) -- vanjo pa tako ali tako ne kaze nobena
povezava.  Torej si lahko zdaj mislimo, kot da te tocke in iz nje
izhajajocih povezav sploh ni vec v grafu -- mislimo si, da smo jih zbrisali
iz grafa.

To, kar nam ostane, je se vedno usmerjen aciklicen graf, le da ima eno tocko
manj (jasno je, da je se vedno aciklicen -- ce prej ni bilo nobenega cikla,
pa smo pobrali ven se nekaj povezav, zdaj se toliko bolj ne bo nobenega
cikla).  Zdaj lahko z enakim razmislekom kot prej izberemo poljubno tocko, v
katero ne vodi nobena povezava, in jo dodamo na drugo mesto v nasem
topoloskem vrstnem redu.  Res, kajti vanjo ne vodi nobena povezava (v
prvotnem grafu je mogoce vanjo vodila kaksna povezava iz prve tocke v
topoloskem vrstnem redu, a to je OK in je v skladu z definicijo topoloskega
vrstnega reda), iz nje pa gotovo ne vodi nobena povezava v prvo tocko
topoloskega vrstnega reda (sicer tiste tocke takrat sploh ne bi postavili na
prvo mesto v topoloski vrstni red), ce pa vodi kaksna povezava v katero od
ostalih tock, je to OK, saj bodo te ostale tocke prisle v topoloskem vrstnem
redu sele kasneje.

Zdaj torej, ko smo postavili v topoloski vrstni red tudi drugo tocko, lahko
tudi njo zbrisemo iz grafa, skupaj z njo pa tudi vse povezave, ki so kazale
iz nje.

S tem lahko zdaj nadaljujemo -- izberemo spet neko tocko, v katero ne kaze
nobena povezava, in jo dodamo v topoloski vrstni red in jo zbrisemo iz
grafa, itd., itd., vse dokler nam ne zmanjka tock.

Abstraktni pogled na topolosko urejanje bi bil torej tak:

1  TopoloskiVrstniRed := (prazen seznam);
2  dokler je v V se kaj tock:
3    naj bo u neka tocka z vhodno stopnjo 0;
4    dodaj u na konec topoloskega vrstnega reda;
5    zbrisi iz V tocko u in vse povezave, ki kazejo iz nje;
6  vrni TopoloskiVrstniRed;

--

Razmislimo zdaj, kaj potrebujemo za ucinkovito implementacijo.  V vrstici
(3) moramo izbrati kaksno tocko z vhodno stopnjo 0 -- torej je prikladno, ce
imamo nek seznam tock z vhodno stopnjo 0.  Ker v grafu povezave le brisemo,
dodajamo pa jih ne, bo tocka, ko ima enkrat vhodno stopnjo 0, tudi ostala
pri vhodni stopnji 0.  Torej, tocka, ki jo dodamo na ta seznam, tudi ostane
na njem, dokler ne pride nekoc na vrsto v vrstici (3), da jo vzamemo iz
grafa.

Nato pa je treba zbrisati iz grafa povezave, ki kazejo iz tocke u; torej
potrebujemo seznam u-jevih naslednikov, kajti povezave, ki kazejo iz u,
gredo ravno v te naslednike.  Ne bomo pa potrebovali seznama predhodnikov;
dovolj je, ce hranimo vhodno stopnjo vsake tocke.  Ko zbrisemo povezavo
u->v, se vhodna stopnja tocke v zmanjsa za 1.  Ce s tem vhodna stopnja tocke
v pade na 0, jo moramo dodati v tisti seznam, o katerem govori prejsnji
odstavek.

Ker tocko, ki jo v vrstici (3) vzamemo iz seznama tock z nicelno vhodno
stopnjo, takoj nato v vrstici (4) dodamo na konec topoloskega vrstnega reda,
je topoloski vrstni red v bistvu cisto enak vrstnemu redu, v katerem
jemljemo tocke v vrstici (3) iz seznama.

Vidimo lahko, da je v vrstici (3) cisto vseeno, katero izmed tock s stopnjo
0 vzamemo.  Recimo, da dodajamo vedno na konec seznama, jemljemo pa vedno z
zacetka.  (Temu pravimo, da nas seznam deluje kot vrsta (queue) -- prvi
noter, prvi ven (FIFO -- first in, first out).)  Potem je topoloski vrstni
red, ki je, kot smo videli zgoraj, enak vrstnemu redu, v katerem jemljemo
tocke iz seznama, obenem tudi cisto enak vrstnemu redu, v katerem dodajamo
tocke v seznam.  Torej je ta seznam ze sam po sebi kar topoloski vrstni red,
ce le ne bomo dejansko jemali tock iz njega, pac pa si le zapomnili, do kod
smo pri njem ze prisli!

Zdaj lahko napisemo naslednjo ucinkovito izvedbo topoloskega urejanja
(predpostavili bomo, da imamo tocke ostevilcene od 1 do n):

(* seznam ali vrsta, v katerem se bo
   nabiral topoloski vrstni red tock *)
var q: array[1..n] of Integer;
(* qHead je "glava" vrste -- indeks prve tocke, ki
   bi jo bilo treba se "vzeti" iz seznama, ce bi
   jih res jemali ven; qTail pa je "rep" vrste --
   indeks, kamor bi dodali naslednjo tocko *)
var qHead, qTail: Integer;
(* inDeg[u] je vhodna stopnja tocke u *)
var inDeg: array[1..n] of Integer;

1. Pripravimo vhodne stopnje vseh tock.
   To najbrz pomeni, da je treba postaviti vse
   inDeg[u] na 0 in nato iti po vseh povezavah
   ter pri vsaki povezavi u->v povecati inDeg[v]
   za 1.
2. Dodajmo v vrsto tocke z nicelno vhodno stopjo.
   qHead := 1; qTail := 1;
   for u := 1 to n do
     if inDeg[u] = 0 then
       begin q[qTail] := u; qTail := qTail + 1; end;
3. Ce bi iz seznama oz. vrste res jemali tocke,
   bi v njem bile vedno le tocke q[qHead..qTail-1].
   Nehati moramo, ko je seznam "prazen", torej
   takrat, ko je qHead = qTail.
   while qHead < qTail do
     begin
     (* v mislih vzamemo naslednjo tocko, u, iz vrste *)
     u := q[qHead]; qHead := qHead + 1;
     for v := (po vseh u-jevih naslednikih) do
       begin
       (* zbrisimo povezavo u->v iz grafa *)
       inDeg[v] := inDeg[v] - 1;
       if inDeg[v] = 0 then
         begin q[qTail] := v; qTail := qTail + 1; end;
       end; (* for *)
     end; (* while *)

To je to.  Na koncu tega postopka je qHead = qTail (= n, torej stevilo tock
v grafu), tabela q pa vsebuje vse tocke v topoloskem vrstnem redu.

No, lahko bi tudi preverili, da ni bilo v grafu, s katerim smo zaceli
postopek, slucajno kaksnega cikla.  To opazimo tako, da se nam seznam
izprazni, se preden obiscemo vse tocke.  Torej, ce se zanka while konca
(qHead = qTail), pa je qTail < nTockVGrafu, je to znak, da v tem, kar je od
grafa se preostalo (po brisanju dosedanjih tock in iz njih izhajajocih
povezav), ni nobene tocke z nicelno vhodno stopnjo, to pa, kot smo videli ze
zgoraj, pomeni, da ta preostanek grafa vsebuje cikel, in potemtakem mora
biti ta cikel prisoten ze v grafu, s katerim smo zaceli delati (ker smo
odtlej povezave le brisali, nikoli pa dodajali).  S tem lahko torej tudi
preverimo, ce ima graf kak cikel.

Vidimo lahko, da je ta postopek kar ucinkovit.  Obiskati mora vse tocke in
pri vsaki vse njene naslednike -- torej ima toliko dela, kolikor je povezav
v grafu, in obenem vsaj toliko, kolikor je tock v grafu (ker mora pripraviti
tabelo inDeg).
Lahko bi torej rekli, da je casovna zahtevnost sorazmerna z |V| + |E|.  [No,
seveda obicajno prevladuje stevilo tock, razen ce ni graf res zelo redek (in
nepovezan).]  To je prav dobra casovna zahtevnost, saj potrebujemo v bistvu
ze za to, da graf sploh od nekod preberemo (npr. iz datoteke) tudi toliko
casa -- prebrati moramo pac vse povezave.  Zato si boljsega od tega skoraj
ne moremo zeleti.

Res pa seveda je, da tale zadnji razmislek temelji na predpostavki, da imamo
sezname naslednikov vsake tocke.  Ce bi imeli matriko sosednosti, bi morali
pri vsakem u preiskati celo vrstico matrike sosednosti in bi bila zato
kolicina dela sorazmerna z |V|^2, ne pa z |V| + |E|.  Skratka, matrika
sosednosti bi nas prisilila, da z vsakim grafom delamo, kot da je zelo gost,
kar je pogosto slabo, ker imamo verjetno veckrat opravka z redkimi grafi kot
z gostimi.